package modelo.dominio.util;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import model.dominio.arbolHuffman.ArbolBinarioHuffman;
import model.dominio.arbolHuffman.NodoBinarioHuffman;
import model.dominio.listasSimples.ListaSimple;
import modelo.dominio.tablasHash.TablasHash;

/**
 *Clase que crea un archivo compreso con la extension .gan 
 *
 * @author GustavoCanul, NoeCob, ArandiLopez
 *
 */
public class Compresor {

    public ArbolBinarioHuffman arbolHuffman;
    private FileInputStream lector;
    private FileInputStream lector2;
    private FileOutputStream escritor;
    private int tabla[];
    private long numeroBytes;
    public static final String EXTENSION_COMPRIMIDO = ".gan";
    private String rutaOrigen, rutaDestino, nombreArchivo;
    TablasHash tablaHash;
    ListaSimple lista;

    /**
     * Cosntructor que inicializa las rutas de origen y destino
     *
     * @param rutaOrigen 
     * @param rutaDestino 
     */
    public Compresor(String nombreArchivo, String rutaOrigen, String rutaDestino) throws IOException {

        this.rutaOrigen = rutaOrigen;
        this.rutaDestino = rutaDestino;
        this.nombreArchivo = nombreArchivo;
        arbolHuffman = new ArbolBinarioHuffman(null);
        tabla = new int[256];
        tablaHash = new TablasHash(rutaOrigen);
        lista = tablaHash.obtenerListaFrecuencias();
      

    }

    /**
     * Permite leer el archivo de entrada para generar la tabla de frecuencias
     * de los bytes correspondientes.
     *
     * @throws IOException
     * @throws FileNotFoundException
     */
    private void leerOrigen() throws IOException, FileNotFoundException {

        int byteLeido;
        numeroBytes = 0;

        lector = new FileInputStream(rutaOrigen);

        do {
            byteLeido = lector.read();

            if (byteLeido != -1) {
                tabla[byteLeido]++;
                numeroBytes++;
            }
        } while (byteLeido != -1);


        lector.close();
        generarHuffman();
    }

    /**
     * Metodo que crea todos los nodos que poseen frecuencias y añadirlos a una lista simple 
     */
    private void generarHuffman() {

        NodoBinarioHuffman aux;

        for (int i = 0; i < tabla.length; i++) {
            if (tabla[i] != 0) {
                aux = new NodoBinarioHuffman((char) i, tabla[i], true);
                arbolHuffman.agregarElemento(aux);
            }
        }

        arbolHuffman.ordenarArbol();
        arbolHuffman.generarArbol();
    }

    /**
     * Metodo principsl para la compresion del archivo
     *
     * @throws IOException
     * @throws FileNotFoundException
     */
    public void comprimir() throws IOException, FileNotFoundException {

        String sobrante = "", registroAuxiliar = "", registroCodigo = "", extension = "";
        int byteLeido = 0;
        byte registroTablaBytes[][] = imprimirTablaBytes();


        //****** Escribe el resto del fichero **************
        do {
            byteLeido = lector2.read();

            if (byteLeido != -1) {
                if (sobrante.length() < 8) {
                    registroCodigo = arbolHuffman.obtenerCodigoBinario((byte) byteLeido);
                } else {
                    registroCodigo = sobrante;
                }

                for (int i = 0; i < registroCodigo.length(); i++) {

                    if (registroAuxiliar.length() == 0 && sobrante.length() != 0) {//a�ade lo sobrante

                        for (int j = 0; registroAuxiliar.length() < 8; j++) {

                            if (sobrante.length() != 0) {
                                registroAuxiliar += sobrante.charAt(0);
                                sobrante = sobrante.substring(1);
                            } else {
                                break;
                            }
                        }
                        if (registroAuxiliar.length() == 8) {
                            registroCodigo = sobrante;
                        }
                    }
                    if (registroAuxiliar.length() < 8) {
                        registroAuxiliar += registroCodigo.charAt(i);
                    } else {
                        sobrante += registroCodigo.substring(i);
                        break;
                    }
                }
                if (registroAuxiliar.length() == 8) {
                    escritor.write(binarioAdecimal(registroAuxiliar));
                    registroAuxiliar = "";
                }
            }
        } while (byteLeido != -1);

        if (sobrante.length() != 0 && sobrante.length() <= 8) {
            for (int m = 0; m < sobrante.length(); m++) {
                registroAuxiliar += sobrante.charAt(m);
            }
        }
        if (registroAuxiliar.length() < 8) {
            for (int j = registroAuxiliar.length(); j < 8; j++) {
                registroAuxiliar += "0";
            }
        }
        escritor.write(binarioAdecimal(registroAuxiliar));

        lector2.close();
    }

    private byte[][] imprimirTablaBytes() throws IOException {
        String extension = "", code = "";
        byte tablaTemp[][];
        leerOrigen();
        lector2 = new FileInputStream(rutaOrigen);
        escritor = new FileOutputStream(rutaDestino + obtenerNombreArchivo(nombreArchivo) + EXTENSION_COMPRIMIDO);

        tablaTemp = arbolHuffman.getTablaBytes();
        extension = obtenerExtensionArchivo(rutaOrigen);


        //******** ESCRIBE TABLA **********

        escritor.write((byte) (numeroBytes % 256));
        numeroBytes /= 256;
        escritor.write((byte) (numeroBytes % 256));
        numeroBytes /= 256;
        escritor.write((byte) (numeroBytes % 256));
        numeroBytes /= 256;
        escritor.write((byte) (numeroBytes % 256));

        //***********Escribe la extension
        for (int i = 0; i < extension.length(); i++) {
            escritor.write(extension.charAt(i));
        }

        escritor.write(arbolHuffman.getTamanoArbol());

        for (int i = 0; i < arbolHuffman.getTamanoArbol(); i++) {
            escritor.write(tablaTemp[i][0]);
            code = arbolHuffman.obtenerCodigoBinario((byte) tablaTemp[i][0]);
            escritor.write(binarioAdecimal(code));
            escritor.write(numerosCero(code));
        }
        return tablaTemp;
    }

    /**
     * Permite saber cuantos ceros tendra delante el codigo huffman
     *
     * @param registroCodigo 
     * @return contador
     */
    private int numerosCero(String code) {
        int contador = 0;
        for (int i = 0; i < code.length(); i++) {
            if (code.charAt(i) == '0') {
                contador++;
            } else {
                break;
            }
        }
        return contador;
    }

    /**
     * Permite convertir una cadena que representa un numero en base 2 a un
     * entero en base 10
     *
     * @param numero es la cadena de binarios
     * @return resultado
     */
    private int binarioAdecimal(String numero) {
        int resultado = 0;

        for (int i = numero.length() - 1, j = 0; i >= 0; i--, j++) {
            resultado += ((int) numero.charAt(j) - 48) * Math.pow(2, i);
        }

        return resultado;
    }

    /**
     * Metodo que retorna la extension del archivo a comprimir
     *
     * @param ruta es el archivo origen
     * @return un String
     */
    private String obtenerExtensionArchivo(String ruta) {
        for (int i = ruta.length() - 1; i >= 0; i--) {
            if (ruta.charAt(i) == '.') {
                return ruta.substring(i);
            }
        }
        return null;
    }
 /**
  * Metodo que obtiene el nombre del archivo
  * @param nombreCompleto
  * @return 
  */
    private String obtenerNombreArchivo(String nombreCompleto) {
        String salida = "";
        for (int i = 0; i < nombreCompleto.length(); i++) {
            if (nombreCompleto.charAt(i) == '.') {
                salida = nombreCompleto.substring(0, i);

                System.out.println(salida);
                return salida;
            }
        }
        return null;
    }
}
